<?php


/* SVN FILE: $Id: download_plugin.php 120 2007-04-18 21:00:13Z Andy $ */

/**
 * Short description for file.php.
 *
 * Long description for file
 *
 * PHP versions 4 and 5
 *
 * Copyright (c), Andy Dawson
 *
 * Licensed under The MIT License
 * Redistributions of files must retain the above copyright notice.
 *
 * @filesource
 * @copyright    Copyright (c) 2007, Andy Dawson
 * @package      noswad
 * @subpackage   noswad.app.controllers.components
 * @since        Noswad site version 3
 * @todo         TODO Edit this block
 * @version      $Revision: 120 $
 * @created      26/01/2007
 * @modifiedby   $LastChangedBy$
 * @lastmodified $Date: 2007-04-18 23:00:13 +0200 (mié, 18 abr 2007) $
 * @license      http://www.opensource.org/licenses/mit-license.php The MIT License
 */

include_once('template.php');
/**
 * Download plugin component
 *
 * Component which will generate a zip file for a specified plugin folder
 */
class DownloadPluginComponent extends TemplateComponent {
	var $name= 'DownloadPlugin'; // required for PHP4 installs
	var $components= array (
		'Zip'
	);
	var $PluginName= null; // Placeholder for the camelCase name of the plugin

	/**
	 * Patterns that will be used to find and replace info in each file included in the download
	 *
	 * @var array
	 * @access public
	 */
	var $replacePatterns= array (
		'@//--Private[^\@]*//--Private End@U' => '',
		'@include_once\(COMPONENTS.\'@U' => 'include_once(\''
	);

	/**
	 * The path to a directory that contains files which are not directly in the plugin, but the plugin
	 * relies upon when not included in this site.
	 *
	 * @var string
	 * @access public
	 */
	var $genericPath= null;

	/**
	 * The type of download to be generated. A plugin is designed to be self sufficient and deployed to
	 * the plugin folder. an app download is designed to be the complete application
	 *
	 * @var string 'plugin' or 'app'
	 * @access public
	 */
	var $mode= 'plugin';

	/**
	 * The end location for the download browser means serving to the browser as a zip file, local
	 * means it will be regenerated on the current install. Local is used to generate working 'real'
	 * copies of the downloads which are of type 'demo'
	 *
	 * @var string 'browser' or 'local'
	 * @access public
	 */
	var $target= 'browser';

	/**
	 * Placeholder for the name of the current plugin
	 *
	 * @var string
	 * @access public
	 */
	var $plugin= '';

	/**
	 * Files to be included in the download which are not in the plugin folder. E.g. components which
	 * are in the app component folder. Necessary to specify the full path to the file.
	 *
	 * @var array
	 * @access public
	 */
	var $extras= array ();

	/**
	 * Files in the plugin folder which are not to be included in the download
	 *
	 * @var array
	 * @access public
	 */
	var $excludes= array ();

	/**
	 * List of files that have been added. Populated in the process of generating the download, not
	 * edited or added to manually
	 *
	 * @var array
	 * @access private
	 */
	var $addedFiles= array ();

	

	function getCachePath() {
	
		if ($this->cachePath) {
			return $this->cachePath;
		}
		$path[]= 'download';
		$path[]= $this->mode;
		$path[]= r('demo_', '',$this->plugin);

		if (empty ($this->controller->params['pass']['confirmed'])) {
			$path[] = 'last_updated';
		} else {
			$path[] = 'zip_contents';
		}
		$cachePath= implode($path, '_').'.cache';
		$this->cachePath= $cachePath;
		return $cachePath;
	}

	function process() {
		//$this->replacePatterns['@adminString_SomethingYouDefine@'] = $this->plugin.'/admin/index';
		$this->excludes= isset ($this->controller->excludes) ? $this->controller->excludes : array ();
		array_push($this->excludes, APP . 'plugins' . DS . $this->plugin . DS . 'read_me.txt');

		$mode= isset ($this->controller->params['pass']['mode']) ? $this->controller->params['pass']['mode'] : false;
		$this->mode= $mode == 'app' ? 'app' : 'plugin';

		$this->plugin= $this->controller->plugin;

		if (empty ($this->controller->params['pass']['confirmed'])) {
			$lastUpdatedDate= $this->_checkCache();
			if (!$lastUpdatedDate) {
				$lastUpdatedDate= $this->_getLastUpdated($this->plugin, $this->extras);
				$this->_saveToCache($lastUpdatedDate);
			}
			$this->_confirmMessage($lastUpdatedDate);
		} else {
			$zipContents = $this->_checkCache();
			if (!$zipContents) {
				
				$this->_build();
				if ($this->target != 'browser') {
					$this->_save();
				} else {
					$this->_saveToCache($this->Zip->getZippedfile());
					$this->_serve();
				}
			} else {
				if (ini_get('zlib.output_compression')) {
					ini_set('zlib.output_compression', 'Off');
				} else {
					header('Pragma: public');
					header('Expires: 0');
					header('Cache-Control: must-revalidate, post-check=0, pre-check=0');
					header('Cache-Control: private', false);
					header('Content-Type: application/zip');
					header('Content-Disposition: attachment; filename=' . r('Demo', '', $this->plugin) . '.zip;');
					header('Content-Transfer-Encoding: binary');
					header('Content-Length: ' . strlen($zipContents));
					echo $zipContents;
					exit (); //skip problems with views
				}
			}
		}
	}
	/**
	 * Display a message of the immenent download and redirect the user to the url corresponding to the
	 * url corresponding to the actual download
	 *
	 * @return null
	 * @access private
	 */
	function _confirmMessage($date) {
		$this->controller->viewPath= '_generic';
		$this->controller->set('url', array('mode'=>$this->mode,'confirmed'=>'confirmed'));
		if ($this->mode == 'plugin') {
			$this->controller->set('message', 'Your download will begin in 5 seconds.');
		} else {
			$this->controller->set('message', 'Your download will begin in 5 seconds. Note: the code to convert a ' .
					'plugin to an app is in Alpha status.');
		}
		$this->controller->set('pluginUnderscore', r('_demo', '', $this->plugin));
		$this->controller->set('pluginCamel', r('Demo', '', $this->plugin));
		$this->controller->set('plugin', Inflector :: Humanize(r('_demo', '', $this->plugin)));

		$this->controller->render('download');
	}

	function _addExtras() {
		// Add the components used to download the plugin, in the hope that other's might do the same :)
		array_push($this->extras, APP . 'controllers' . DS . 'components' . DS . 'download_plugin.php');
		array_push($this->extras, APP . 'controllers' . DS . 'components' . DS . 'zip.php');

		// Add the menu stuff
		array_push($this->extras, APP . 'views' . DS . 'elements' . DS . 'side_menu.thtml');
		array_push($this->extras, APP . 'views' . DS . 'elements' . DS . 'side_generic.thtml');

		$this->extras= am(isset ($this->controller->extras) ? $this->controller->extras : array (), $this->extras);

		// Automatically add inherited components, models, behaviors and helpers.
		$components= array ();
		$helpers= array ();
		$models= array ();
		$behaviors= array ();
		$searchFor= array ();

		$classes= listClasses(APP . 'plugins' . DS . $this->plugin . DS . 'controllers');
		array_unshift($classes, $this->plugin . '_app_controller.php');
		$path= APP . 'plugins' . DS . $this->plugin . DS;
		foreach ($classes as $i => $controller) {
			include_once ($path . $controller);
			if (!$i) {
				$path= $path . 'controllers' . DS;
			}
			list ($name)= explode('.', $controller);
			$className= Inflector :: camelize($name);
			$controller_vars= get_class_vars($className);
			$components= am($components, $controller_vars['components']);
			$helpers= am($helpers, $controller_vars['helpers']);
			if ($controller_vars['uses']) {
				$models= am($models, $controller_vars['uses']);
			}
		}
		$appController_vars= get_class_vars('AppController');

		$components= array_unique($components);
		$components= array_diff($components, $appController_vars['components']);
		foreach ($components as $component) {
			$searchFor[]= 'controllers' . DS . 'components' . DS . Inflector :: underscore($component) . '.php';
		}

		$helpers= array_unique($helpers);
		$helpers= array_diff($helpers, $appController_vars['helpers']);
		foreach ($helpers as $helper) {
			$searchFor[]= 'views' . DS . 'helpers' . DS . Inflector :: underscore($helper) . '.php';
		}

		$classes= listClasses(APP . 'plugins' . DS . $this->plugin . DS . 'models');
		array_unshift($classes, $this->plugin . '_app_model.php');
		$path= APP . 'plugins' . DS . $this->plugin . DS;
		foreach ($classes as $i => $model) {
			include_once ($path . $model);
			if (!$i) {
				$path= $path . 'models' . DS;
			}
			list ($name)= explode('.', $model);
			$className= Inflector :: camelize($name);
			$model_vars= get_class_vars($className);
			if ($model_vars['actsAs']) {
				$behaviors= am($behaviors, $model_vars['actsAs']);
			}
		}
		$appModel_vars= get_class_vars('AppModel');

		$models= array_unique($models);
		foreach ($models as $model) {
			$searchFor[]= 'model' . DS . Inflector :: underscore($model) . '.php';
		}

		$behaviors= array_unique($behaviors);
		if ($appModel_vars['actsAs']) {
			$behaviors= array_diff($behaviors, $appModel_vars['actsAs']);
		}
		foreach ($behaviors as $behavior) {
			$searchFor[]= 'models' . DS . 'behaviors' . DS . Inflector :: underscore($behavior) . '.php';
		}

		foreach ($searchFor as $file) {
			if ((!in_array(APP_DIR . 'plugins' . DS . $this->plugin . DS . $file, $this->excludes)) && (!file_exists(APP . 'plugins' . DS . $this->plugin . DS . $file)) && (file_exists(APP . $file))) {
				$this->extras[]= APP . $file;
			}
		}
	}

	/**
	 * Generate the files and folders for the download
	 *
	 * @return null
	 * @access private
	 */
	function _build() {
		$this->_addExtras();

		if (strpos($this->plugin, 'demo')) {
			$this->replacePatterns['@(' . $this->plugin . ')@']= r('_demo', '', $this->plugin);
			$this->replacePatterns['@(' . $this->plugin . ')@']= r('Demo', '', $this->plugin);
		}
		// Plugin App controller handled seperately
		$pluginApp= file_get_contents(APP . 'plugins' . DS . $this->plugin . DS . $this->plugin . '_app_controller.php');
		if ($this->mode == 'plugin') {
			$base= 'app' . DS . 'plugins' . DS . $this->plugin . DS;
			// Change controller parent
			$pluginApp= r('<?php', "<?php\r\ninclude('generic_controller.php');", $pluginApp);
			$pluginApp= r('extends AppController', 'extends GenericController', $pluginApp);
			$this->__includeContents($pluginApp, $base . $this->plugin . '_app_controller.php', filectime(APP . 'plugins' . DS . $this->plugin . DS . $this->plugin . '_app_controller.php'));
		} else {
			$this->replacePatterns['@(extends ' . $this->plugin . ')@']= 'extends ';
			$this->replacePatterns['@(/' . $this->plugin . '.)@']= '';
			$this->replacePatterns['@(' . $this->plugin . '/)@']= '';

			$base= 'app' . DS;
			$this->__includeContents($pluginApp, $base . 'app_controller.php', filectime(APP . 'plugins' . DS . $this->plugin . DS . $this->plugin . '_app_controller.php'));
			$pluginApp= file_get_contents(APP . 'plugins' . DS . $this->plugin . DS . $this->plugin . '_app_model.php');
			$pluginApp= r($this->plugin . 'AppModel extends App', 'AppModel  extends ', $pluginApp);
			$this->__includeContents($pluginApp, $base . 'app_model.php', filectime(APP . 'plugins' . DS . $this->plugin . DS . $this->plugin . '_app_model.php'));
		}

		// include every file from the extras list, but put it in the plugin
		array_push($this->extras, APP . 'controllers' . DS . 'components' . DS . 'download_plugin.php');
		array_push($this->extras, APP . 'controllers' . DS . 'components' . DS . 'zip.php');
		foreach ($this->extras as $file) {
			if (strstr($file, APP_DIR)) {
				$relativePath= substr($file, strlen(APP));
				$relativePath= $base . $relativePath;
			}
			elseif (strstr($file, VENDORS)) {
				$relativePath= substr($file, strlen(VENDORS));
				$relativePath= 'vendors' . DS . $relativePath;
			} else {
				$relativePath= $base . 'webroot' . DS . substr($file, strlen(WWW_ROOT));
			}
			$this->__includeFile($file, $relativePath);
		}

		// include every file from the plugin
		uses('Folder');
		$Folder= new Folder(APP . 'plugins' . DS . $this->plugin . DS);
		$FileList= $Folder->findRecursive();
		unset($Folder);
		foreach ($FileList as $file) {
			$relativePath= 'app' . DS . substr($file, strlen(APP));
			$this->__includeFile($file, $relativePath);
		}

		// include every file from the generic include folder
		if ($this->genericPath) {
			$Folder= new Folder($this->genericPath);
			$FileList= $Folder->findRecursive();
			unset($Folder);
			foreach ($FileList as $file) {
				$relativePath= substr($file, strlen($this->genericPath));
				if ($relativePath <> 'read_me.txt') {
					$relativePath= $base . $relativePath;
					$this->__includeFile($file, $relativePath);
				} else {
					$relativePath= r('_demo', '', $this->plugin) . '_read_me.txt';
					$readMe= file_get_contents($file);
					if (file_exists(APP . 'plugins' . DS . $this->plugin . DS . 'read_me.txt')) {
						$specificReadMe= file_get_contents(APP . 'plugins' . DS . $this->plugin . DS . 'read_me.txt');
					} else {
						$specificReadMe= '-- No specific notes --';
					}
					if (in_array('app' . DS . 'config' . DS . 'routes.php', $this->addedFiles)) {
						$routeNotes= '4) Optionally integrate the contents of /app/config/routes.php into your application routes file.';
					} else {
						$routeNotes= '';
					}

					$readMePatterns= array (
						'@\$PluginName\$@' => r('Demo',
						'',
						Inflector :: Humanize($this->plugin
					)), '@\$PluginCamelName\$@' => r('Demo', '', r('Demo', '', $this->plugin)), '@\$PluginDirName\$@' => r('_demo', '', $this->plugin), '@\$SiteVersion\$@' => SITE_VERSION, '@\$CakeVersion\$@' => Configure :: version(), '@\$PluginNotes\$@' => $specificReadMe, '@\$RouteNotes\$@' => $routeNotes);
					foreach ($readMePatterns as $replacePattern => $replacement) {
						$readMe= preg_replace($replacePattern, $replacement, $readMe);
					}
					$this->__includeContents($readMe, $relativePath, filectime($file));
				}
			}
		}
		$this->__includeFile(ROOT . DS . '..' . DS . 'cake.diff', 'cake.diff', false);
	}

	/**
	 * send the zip file to the browser
	 *
	 * @return null
	 * @access private
	 */
	function _serve() {
		$this->Zip->forceDownload(r('Demo', '', $this->plugin) . '.zip');
	}

	/**
	 * Save the contents of the demo plugin to the current install path, minus 'demo' from the name.
	 * I.e. if triggered from somethingDemo plugin, will generate a fully functional 'something'
	 * plugin.
	 *
	 * @return null
	 * @access private
	 */
	function _save($target='local') {
		uses('File');
		uses('Folder');
		if ($target == ' local') {
			$root = ROOT . DS;

		}
		if (file_exists($root.APP_DIR . DS . 'plugins' . DS . $this->plugin)) {
			$Folder = new Folder($root.APP_DIR . DS . 'plugins' . DS . $this->plugin);
			foreach ($Folder->findRecursive() as $file) {
				unlink($file);
			}
			unset($Folder);
		}
		foreach ($this->fileContents as $path => $contents) {
			$File= new File($root . $path, true,0777);
			$Folder = $File->getFolder();
			$Folder->mkdirr($Folder->path,0777);
			if (!($File->write($contents) && $File->exists())) {
				kill('failed creating ' . ROOT . DS . $path . ' exiting.');
			}
			unset ($File);
		}
		kill ('Local version generated.');
		$this->controller->redirect($this->controller->referer('/' . r('Demo', '', $this->plugin)));
	}

	/**
	 * For each file in the plugin, find the date last update. Return the most recent of these dates
	 *
	 * @param string $Plugin name of the plugin to query
	 * @return date last updated
	 * @access private
	 */
	function _getLastUpdated($Plugin) {
		uses('Folder');
		$Folder= new Folder(APP . 'plugins' . DS . $Plugin . DS);
		$FileList= am($Folder->findRecursive(), $this->extras);
		unset($Folder);
		if ($this->genericPath) {
			$Folder= new Folder($this->genericPath);
			$Generic= $Folder->findRecursive();
		} else {
			$Generic= array ();
		}
		$FileList= am($Generic, $FileList);
		$date= 0;
		foreach ($FileList as $File) {
			$FileDate= filemtime($File);
			if ($FileDate > $date) {
				$date= $FileDate;
			}
		}
		return $date;
	}

	/**
	 * Include the specified file in the download
	 *
	 * @param string $File full path to the file to include
	 * @param string $relativePath path to be used in the download
	 * @param string $parseVars whether to run the find and replace code
	 * @return null
	 * @access private
	 */
	function __includeFile($File, $relativePath, $parseVars= true) {
		if ((strpos($File, 'svn') === false) && (!in_array($relativePath, $this->excludes)) && (!in_array($relativePath, $this->addedFiles)) && (file_exists($File))) {
			$this->__includeContents(file_get_contents($File), $relativePath, filectime($File), $parseVars);
		}

	}

	/**
	 * Include the specified content in the download
	 *
	 * @param string $Contents the contents of a file
	 * @param string $relativePath path to be used in the download
	 * @param string $time the time to include in the download of the time the file was updated
	 * @param string $parseVars whether to run the find and replace code
	 * @return null
	 * @access private
	 */
	function __includeContents($Contents, $relativePath, $time= 0, $parseVars= true) {
		// Allow for overriding of included content
		$relativePath= r('_demo', '', $relativePath);
		if ((!in_array($relativePath, $this->excludes)) && (!in_array($relativePath, $this->addedFiles))) {
			if ($parseVars) {
				foreach ($this->replacePatterns as $replacePattern => $replacement) {
					$Contents= preg_replace($replacePattern, $replacement, $Contents);
				}
			}
			if ($this->target != 'browser') {
				$this->fileContents[$relativePath]= $Contents;
			} else {
				$this->Zip->addData($Contents, $relativePath, $time);
			}
			$this->addedFiles[]= $relativePath;
		}
	}

	function _continue(& $controller) {
		return true;
	}
}
?>